# Copyright 2012 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License.  You may obtain a copy
# of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
# License for the specific language governing permissions and limitations
# under the License.

"""Request handlers."""

import config
import datetime
import logging
import models
import urllib
import utility

from google.appengine.api import images
from google.appengine.api import urlfetch
from google.appengine.api import users
from google.appengine.ext import db
from google.appengine.ext import webapp
from google.appengine.ext.webapp import template as webapp_template


class RequestHandler(webapp.RequestHandler):
  """Provides common functionality for use in other RequestHandlers."""
  def RenderTemplateOut(self, template_name, template_vars=None):
    template_vars = template_vars or {}
    template_vars['application_url'] = (
        config.embedded_url_prefix or self.request.application_url
      )
    template_vars['embedded_url_prefix'] = config.embedded_url_prefix

    self.response.out.write(webapp_template.render(
        'templates/%s' % template_name, template_vars))

  def ServeHttpError(self, status_code=400, message=''):
    self.error(status_code)
    self.response.out.write(message)

  def GetUserId(self):
    return str(self.request.remote_addr)

  def GetName(self):
    name = self.request.get('name')
    self.response.set_cookie('name', name)
    return name

  def RedirectTop(self, redirect_url):
    if config.embedded_url_prefix:
      redirect_url = config.embedded_url_prefix + redirect_url
      self.RenderTemplateOut('redirect.html', { 'redirect_url': redirect_url })
    else:
      self.redirect(redirect_url)




##############################################################################
# Handlers that are only visible to administrators.

class AdminHandler(RequestHandler):
  """CRUD for contests."""

  def get(self):
    contests = models.Contest.all().order('-end_time')
    self.RenderTemplateOut('admin.html', {'contests': contests})


class AdminContestHandler(RequestHandler):
  """Implements the form that allows the admin to create a new contest."""

  def get(self, contest_key=None):
    contest = None
    if contest_key:
      contest = db.get(db.Key.from_path('Contest', int(contest_key)))
    else:
      start_time = utility.DateNow()
      end_time = start_time + datetime.timedelta(weeks=4)
      contest = models.Contest(name='',
                               description='',
                               creator=self.GetName(),
                               start_time=start_time,
                               end_time=end_time,
                               photo_credit='')

    self.RenderTemplateOut('new_contest.html', {
          'contest': contest,
          'name': self.request.cookies.get('name', '')
        })

  def post(self, contest_key=None):
    # process the request and redirect to the main admin page when done
    new_contest = ''

    try:
      if contest_key:
        new_contest = self.UpdateContest(contest_key)
      else:
        new_contest = self.MakeContest()

    except models.EmptyContestName:
      return self.ServeHttpError(400,
                                'Please specify a valid contest name')
    except images.BadImageError:
      return self.ServeHttpError(400,
                                'Sorry, we had a problem processing'
                                'the image provided.')
    except images.NotImageError:
      return self.ServeHttpError(400,
                                'Sorry, we don\'t recognize that image format.'
                                'We can process JPEG, GIF, PNG, BMP, TIFF, '
                                'and ICO files.')
    except images.LargeImageError:
      return self.ServeHttpError(400,
                                'Sorry, the image provided was too large '
                                'for us to process.')
    except ValueError:
      return self.ServeHttpError(400,
                                'Please enter the date in the required '
                                'format (MM-DD-YYYY)')
    except AttributeError:
      return self.ServeHttpError(400, 'Missing image file')
    except Exception, e:
      logging.error(
          'Hit a general error while trying to create a contest: %s', e)
      return self.ServeHttpError(400, 'Unknown error occured')
    else:
      if contest_key:
        self.redirect('/admin')
      else:
        self.RedirectTop('/contest/%s' % new_contest)

  def MakeContest(self):
    name = self.request.get('contest_title')
    desc = self.request.get('contest_description')
    start = self.request.get('contest_start_time')
    end = self.request.get('contest_end_time')
    disabled = self.request.get('disabled') != ''
    photo = self.request.get('photo_credit')

    username = self.GetName()

    try:
      image_data = self.request.POST.get('contest_image').file.read()
    except (images.BadImageError, AttributeError):
      resp = urlfetch.fetch(self.request.get('photo_url'))
      if resp.status_code != 200:
        raise images.BadImageError()
      image_data = resp.content
    return models.UploadContest(name, desc, username, image_data,
                                start, end, disabled, photo)

  def UpdateContest(self, contest_key):
    contest = db.get(db.Key.from_path('Contest', int(contest_key)))
    contest.name = self.request.get('contest_title')
    contest.description = self.request.get('contest_description')
    contest.creator = self.GetName()
    contest.start_time = utility.ParseDate(self.request.get('contest_start_time'))
    contest.end_time = utility.ParseDate(self.request.get('contest_end_time'))
    contest.disabled = self.request.get('disabled') != ''
    contest.photo_credit = self.request.get('photo_credit')

    if not contest.name:
      raise models.EmptyContestName()

    contest.put()
    return contest_key


class AdminReviewHandler(RequestHandler):
  def get(self):
    page = int(self.request.get('page', 0))
    pageSize = 25
    entries = (models.ContestEntry.all()
                                 .order('-creation_time')
                                 .fetch(offset=(page*pageSize), limit=pageSize))
    self.RenderTemplateOut('review.html', {
        'entries': entries,
        'page': page,
        'previous': page > 0,
        'next': (page + 1)*pageSize <= models.ContestEntry.all().count()
        })


class UpdateReviewHandler(RequestHandler):
  def post(self, entry_key):
    entry = db.get(db.Key.from_path('ContestEntry', int(entry_key)))
    if not entry:
      return self.ServeHttpError(404, 'Unknown entry')

    entry.disabled = self.request.POST['disabled'].lower() == 'true'
    entry.put()
    self.response.out.write('OK')


##############################################################################
# Handlers to serve images.

class ContestImageHandler(RequestHandler):
  def get(self, pic_key):
    # TODO: everywhere that we do something like db.get(), serve 404 if this
    # is not a valid key or doesn't exist in the DB.
    image = db.get(db.Key.from_path('ContestImage', int(pic_key)))
    self.response.headers['Content-Type'] = image.mime_type.encode('utf8',
                                                                   'ignore')
    self.response.out.write(image.image_data)


class MemeImageHandler(RequestHandler):
  def get(self, pic_key):
    image = db.get(db.Key.from_path('ContestEntry', int(pic_key)))
    if image.disabled:
      return self.ServeHttpError(404, 'Entry not found')

    self.response.headers['Content-Type'] = image.mime_type.encode('utf8',
                                                                   'ignore')
    self.response.out.write(image.image_data)


##############################################################################
# Handlers that are publicly accessible.

class VoteHandler(RequestHandler):
  """Registers a vote for a specific contest entry."""
  def post(self, entry_key):
    entry = db.get(db.Key.from_path('ContestEntry', int(entry_key)))
    if entry:
      if entry.contest.IsActive():
        result = db.run_in_transaction(entry.RecordVote, self.GetUserId())
        self.response.out.write(result)
        self.response.out.write(',')
        self.response.out.write(entry.hasVoted(self.GetUserId()))
      else:
        self.ServeHttpError(400, 'Contest no longer open for voting.')
    else:
      self.ServeHttpError(400, 'Invalid "id" parameter.')


class SaveHandler(RequestHandler):
  """Saves the new contest entry."""

  def post(self):
    contest_key = self.request.get('contest_key')
    contest = db.get(db.Key.from_path('Contest', int(contest_key)))
    if not contest:
      return self.ServeHttpError(404, 'Contest not found')
    if not contest.IsActive():
      return self.ServeHttpError(400, 'Contest not active')

    name = self.GetName()
    save_vars = {
        'contest_key': contest_key,
        'topalign': self.request.get('topalign'),
        'middlealign': self.request.get('middlealign'),
        'bottomalign': self.request.get('bottomalign'),
        'top': self.request.get('top'),
        'middle': self.request.get('middle'),
        'bottom': self.request.get('bottom'),
        'font': self.request.get('font'),
        'name': name
    }

    new_entry = 0
    try:
      new_entry = models.SaveEntry(save_vars)
    except models.EmptyEntryText:
      return self.ServeHttpError(400, 'Be funny. Add text.')
    except models.CaptionGenerationFailure:
      return self.ServeHttpError(400, 'Something not funny happened.')
    else:
      self.RedirectTop('/meme/' + str(new_entry) + '?created=true')


class PreviewHandler(RequestHandler):
  """Displays the preview of a contest entry."""
  def get(self):
    contest = db.get(db.Key.from_path(
        'Contest', int(self.request.get('contest_key'))))

    # TODO: check that the font is legit.
    font = self.request.get('font')

    caption_vars = {
        'width': contest.image.width,
        'height': contest.image.height,
        'topalign': self.request.get('topalign'),
        'middlealign': self.request.get('middlealign'),
        'bottomalign': self.request.get('bottomalign'),
        'top': self.request.get('top'),
        'middle': self.request.get('middle'),
        'bottom': self.request.get('bottom'),
        'font': self.request.get('font')
    }

    caption_url = models.GetCaptionUrl(caption_vars)

    self.redirect(caption_url)


class MemeHandler(RequestHandler):
  """Displays the information for a specific entry."""

  def get(self, entry_key):
    entry = db.get(db.Key.from_path('ContestEntry', int(entry_key)))
    if entry.disabled:
      return self.ServeHttpError(404, 'Entry not found')

    completed = entry.contest.IsCompleted

    template_vars = {
        'contest': entry.contest,
        'entry': {'record': entry,
                  'hasVoted': entry.hasVoted(self.GetUserId())},
        'show_ranking': False,
        'vote_deadline': entry.contest.end_time,
        'completed': completed,
        'created': self.request.get('created') == 'true'
    }
    self.RenderTemplateOut('meme.html', template_vars)


class ContestHandler(RequestHandler):
  """Displays the winners and top entries for a given contest."""

  def get(self, contest_key):
    contest = db.get(db.Key.from_path('Contest', int(contest_key)))
    if not contest or contest.disabled:
      return self.ServeHttpError(404, 'Contest Not Found')

    sortBy = self.request.get('sortBy')
    completed = contest.IsCompleted()
    active = contest.IsActive()
    page = self.request.get('page', 0)

    template_vars = {
        'contest': contest,
        'completed': completed,
        'active': active,
        'show_ranking': True,
        'sortBy': sortBy,
        'page': page
    }

    if active:
        template_vars.update({
          'fonts': [
            ('Coda', 'Coda_Heavy'),
            ('Alegreya', 'Alegreya_Black'),
            ('Fell Double Pica', 'Fell_Double_Pica_Italic'),
            ],
          'name': self.request.cookies.get('name', '')
          })

    GetEntries(10, int(page), sortBy, contest, self.GetUserId(), template_vars),
    self.RenderTemplateOut('contest.html', template_vars)


def GetEntries(num, page, sortBy, contest, user, template_vars):
  query = (models.ContestEntry.all()
            .filter('contest =', contest)
            .filter('disabled =', False))
  if sortBy == 'recent':
    sortKey = '-creation_time'
  else:
    sortKey = '-score'
  entries = query.order(sortKey)[(page * num):(num + 1)]
  if len(entries) > num:
    entries.pop()
    template_vars['next'] = True
  if page > 0:
    template_vars['previous'] = True
  template_vars['entries'] = map(
      (lambda x: {'record': x, 'hasVoted': x.hasVoted(user)}), entries)
  template_vars['numEntries'] = query.count()


class ContestListHandler(RequestHandler):
  """Displays all current contests."""

  def get(self):
    page = self.request.get('page', 0)

    template_vars = {
        'page': page
        }
    GetContests(int(page), 5, template_vars)
    self.RenderTemplateOut('contest_list.html', template_vars)


def GetContests(page, num, template_vars):
  """Gets num requested contests, starting from contest number offset."""
  start = (page*num)
  entries = (models.Contest.all()
                           .filter('start_time <=',utility.DateNow())
                           .filter('disabled ==',False)
                           .order('-start_time')
      )[start:(start + num + 1)]
  if len(entries) > num:
    entries.pop()
    template_vars['next'] = True
  if page > 0:
    template_vars['previous'] = True
  template_vars['contests'] = entries


class CurrentContestHandler(RequestHandler):
  """Redirects to the current contest."""
  # doesn't use RedirectTop as we don't want the outer URL to update

  def get(self):
    # find the active contest with the earliest end time
    contest = (models.Contest.all()
                           .filter('end_time >',utility.DateNow())
                           .filter('disabled ==',False)
                           .order('end_time')).get()
    if contest and contest.IsActive():
      return self.redirect('/contest/' + str(contest.key().id()))

    # if none, then find the completed contest with the lastest end time
    contest = (models.Contest.all()
                           .filter('end_time <=',utility.DateNow())
                           .filter('disabled ==',False)
                           .order('-end_time')).get()
    if contest:
      return self.redirect('/contest/' + str(contest.key().id()))

    # if nothing - give up and show the list
    self.redirect('/all')

